#include <iostream>
#include <regex>
#include <fstream>
#include <filesystem>

#include "Scanner.h"
#include "create_tablt_sql_define.hpp"

void buildSS() {
    auto *s = new std::vector<std::string>({
        "create",
        "table",

        "primary",
        "key",
        "autoincrement",

        "integer",
        "text"
    });

    for (auto &i: *s) {
        for (auto &j: i) {
            std::cout << "[" << j << (char) (j - 32) << "]";
        }
        std::cout << std::endl;
    }
    free(s);
}

std::string toUpperCase(std::string &s) {
    std::string res;
    for (char &i: s) {
        res += static_cast<char>(std::toupper(i));
    }
    return res;
}
// 大驼峰命名转换成小驼峰
std::string toLittleCamelCase(std::string &s) {
    char c = static_cast<char> (tolower(s.at(0)));
    return c + s.substr(1);
}
// 把第一个字符变成大写
std::string toFirstUpper(std::string &s) {
    char c = static_cast<char> (toupper(s.at(0)));
    return c + s.substr(1);
}


std::string buildColumnQueryKey(std::string &tbName, std::string &colName) {
    return toUpperCase(tbName) + "_" + toUpperCase(colName);
}

std::string buildTableName(std::string &tbName) {
    return "TABLE_" + toUpperCase(tbName);
}

std::string buildTableNameDefine(std::string &tbName) {
    std::string res;
    res.append("public static final String ").append(buildTableName(tbName))
            .append(" = ").append("\"").append(tbName).append("\"").append(";\n");
    return res;
}

std::string buildColumnInsertKey(std::string &tbName, std::string &colName) {
    return "NOTABLE_" + toUpperCase(tbName) + "_" + toUpperCase(colName);
}

std::string buildColumnQueryDefine(std::string &tbName, std::string &colName) {
    std::string res;
    res.append("public static final String ").append(buildColumnQueryKey(tbName, colName))
            .append(" = ").append("\"").append(tbName).append(".").append(colName).append("\"").append(";\n");
    return res;
}

std::string buildColumnInsertDefine(std::string &tbName, std::string &colName) {
    std::string res;
    res.append("public static final String ").append(buildColumnInsertKey(tbName, colName))
    .append(" = ").append("\"").append(colName).append("\"").append(";\n");
    return res;
}

int main(int argc, char *argv[]) {
    // buildSS();
    // if (argc != 2) {std::cout << "Usage: " << argv[0] << " <filename>" << std::endl; return EXIT_FAILURE;}
    // std::fstream sqlFile = std::fstream(argv[1], std::ios::in | std::ios::binary);
    // if (!sqlFile.is_open()) {std::cout << "File not found" << std::endl; return EXIT_FAILURE;}
    auto *gCtx = new std::unordered_map<std::string, std::string>();

    std::string outputDir = "output";
    if (!std::filesystem::exists(outputDir)) {
        std::filesystem::create_directory(outputDir);
    }
    std::string modelOutputDir = outputDir + "/model";
    if (!std::filesystem::exists(modelOutputDir)) {
        std::filesystem::create_directory(modelOutputDir);
    }
    std::string queryOutputDir = outputDir + "/query";
    if (!std::filesystem::exists(queryOutputDir)) {
        std::filesystem::create_directory(queryOutputDir);
    }

    std::fstream config = std::fstream("resources/global_ctx", std::ios::in);
    Scanner configScanner(config);
    while (configScanner.lex()){}
    auto itg = configScanner.gCtx->begin();
    while (itg != configScanner.gCtx->end()) {
        gCtx->emplace(itg->first, itg->second);
        ++itg;
    }

    config.close();

    std::fstream sqlFile = std::fstream("resources/schema.sql", std::ios::in);
    if (!sqlFile.is_open()) {
        std::cout << "File not found" << std::endl;
        return EXIT_FAILURE;
    }

    Scanner scanner(sqlFile);
    CreateTableSqlDefine *c = nullptr;
    const auto res = new std::vector<CreateTableSqlDefine *>();
    while (const TOKEN r = scanner.nextToken()) {
        switch (r) {
            case CREATE: {
                if (c != nullptr) res->push_back(c);
                c = new CreateTableSqlDefine();
                if (scanner.nextToken() == TABLE) {
                    c->name = scanner.nextMatched();
                    // std::cout << "table name is \t" << scanner.nextMatched() << std::endl;
                }
                break;
            };
            case ID: {
                std::string column = scanner.matched();
                if (c == nullptr) {
                    std::cout << "TBALE ERROR" << std::endl;
                    break;
                }
                switch (scanner.nextToken()) {
                    case TEXT: {
                        auto *col = new Column(SQL_TYPE::TEXT);
                        col->name = column;
                        c->columns->push_back(col);
                        break;
                    }
                    case INTEGER: {
                        auto *col = new Column(SQL_TYPE::INTEGER);
                        col->name = column;
                        col->sqlType = SQL_TYPE::INTEGER;
                        c->columns->push_back(col);
                        // std::cout << "column name is \t" << column << std::endl;
                        break;
                    }
                    default: std::cout << "Parse Column ERROR!!" << std::endl;
                }
                break;
            }
        }
    }
    if (c != nullptr) res->push_back(c);

    std::fstream out3 = std::fstream( modelOutputDir + "/" + "insert.txt", std::ios::out);
    std::fstream out4 = std::fstream(outputDir + "/" + "TABLE_KEYS.txt", std::ios::out);
    for (auto &i: *res) {
        i->status_map->emplace("BACK_MODEL", i->model_suffix.append(i->name));
        i->status_map->emplace("BACK_MODEL_ARG_NAME",  toLittleCamelCase(i->status_map->at("BACK_MODEL")));
        i->status_map->emplace("BACK_MODEL_LIST_ARG_NAME",  i->status_map->at("BACK_MODEL") + "s");

        i->status_map->emplace("TABLE_NAME", buildTableName(i->name));
        i->status_map->emplace("TABLE_NAME_DEFINE", buildTableNameDefine(i->name));

        bool isFirst = true;
        std::string query;
        std::string init;
        std::string filed;
        std::string constructAll;
        std::string constructNull;
        constructNull.append("public ").append(i->status_map->at("BACK_MODEL")).append("(){}\n");
        constructAll.append(constructNull);
        std::vector<std::string> params;
        std::string param;
        std::string setParam;
        std::string inParcelable;
        std::string outParcelable;
        std::string getterAndSetter;
        std::string insertCtx;
        std::string tableKeys;
        std::string noTableKeys;
        std::string allTableKeys;

        // std::string noableKeys;
        for (auto &j: *i->columns) {
            std::string colJavaType = j->javaType();
            if (isFirst) { isFirst = false; } else {
                query.append(".append(',')");
                param.append(", ");
            }
            std::string construct;
            param.append(j->javaType().append(" ").append(j->name));
            setParam.append("\n\tthis.set").append(toFirstUpper(j->name)).append("(").append(j->name).append(");");
            construct.append("public ").append(i->status_map->at("BACK_MODEL")).append("(").append(param).append("){")
            .append(setParam).append("\n}");
            constructAll.append(construct.append("\n"));

            inParcelable.append("this.").append(j->name).append(" = ")
            .append("in.read").append(toFirstUpper(colJavaType)).append("();\n");

            outParcelable.append("out.write").append(toFirstUpper(colJavaType)).append("(").append("this.").append(j->name).append(");\n");

            getterAndSetter.append("public ").append(colJavaType).append(" get").append(toFirstUpper(j->name))
            .append(" () {").append("\n\treturn ").append(j->name).append(";\n}\n")
            .append("public ").append(i->status_map->at("BACK_MODEL")).append(" set").append(toFirstUpper(j->name))
            .append("(").append(colJavaType).append(" ").append(j->name).append(") {\n\t").append("this.").append(j->name)
            .append(" = ").append(j->name).append(";\n\treturn this;\n}\n");

            query.append(".append(").append(buildColumnQueryKey(i->name, j->name)).append(")\n");

            insertCtx.append("ctv.put(").append(buildColumnInsertKey(i->name,j->name))
            .append(", ").append(i->status_map->at("BACK_MODEL_ARG_NAME")).append(".get").append(toFirstUpper(j->name))
            .append("()").append(");\n");
            tableKeys.append(buildColumnQueryDefine(i->name, j->name));
            noTableKeys.append(buildColumnInsertDefine(i->name, j->name));

            init.append(i->status_map->at("BACK_MODEL_ARG_NAME")).append(".set")
            .append(static_cast<char>(toupper(j->name[0]))+j->name.substr(1)).append("(cursor.get")
            .append(static_cast<char>(toupper(j->javaType()[0]))+j->javaType().substr(1)).append("(i++));\n");
            filed.append("private ").append(j->javaType()).append(" ").append(j->name).append(";\n");
        }
        query.append(".append(\" FROM \").append(").append(buildTableName(i->name)).append(")");
        i->status_map->emplace("QUERY", query);
        i->status_map->emplace("BACK_MODEL_ARG_NAME_INIT", init);
        i->status_map->emplace("MODEL_FIELD_LIST", filed);
        i->status_map->emplace("MODEL_CONSTRUCT_LIST", constructAll);
        i->status_map->emplace("PARCELABLE_IN_LIST", inParcelable);
        i->status_map->emplace("GETTER_AND_SETTER_LIST", getterAndSetter);
        i->status_map->emplace("PARCELABLE_OUT_LIST", outParcelable);
        i->status_map->emplace("INSERT_CONTEXT_VALUES", insertCtx);
        i->status_map->emplace("TABLE_KEYS", allTableKeys.append(tableKeys).append(noTableKeys));


        // std::cout << insertCtx << std::endl;
        // std::cout << query << std::endl;
        // std::cout << init << std::endl;
        std::fstream out = std::fstream(queryOutputDir + "/" + i->status_map->at("BACK_MODEL") + ".txt", std::ios::out);
        std::fstream in = std::fstream("resources/temp/query", std::ios::in);
        Scanner tempG(in, out);
        tempG.setMCtx(gCtx,*i->status_map);
        while (tempG.lex()){}
        out.close();
        in.close();


        std::fstream out2 = std::fstream(modelOutputDir + "/" + i->status_map->at("BACK_MODEL") + ".txt", std::ios::out);
        std::fstream in2 = std::fstream("resources/temp/model", std::ios::in);
        Scanner tempG2(in2, out2);
        std::string imports = "import android.os.Parcel;\nimport android.os.Parcelable;\nimport androidx.annotation.NonNull;";
        i->status_map->emplace("IMPORTS", imports);
        tempG2.setMCtx(gCtx,*i->status_map);
        while (tempG2.lex()){}
        out2.close();
        in2.close();

        std::fstream in3 = std::fstream("resources/temp/insert", std::ios::in);
        Scanner tempG3(in3, out3);
        tempG3.setMCtx(gCtx,*i->status_map);
        while (tempG3.lex()){}
        in3.close();

        std::fstream in4 = std::fstream("resources/temp/TABLE_KEYS", std::ios::in);
        Scanner tempG4(in4, out4);
        tempG4.setMCtx(gCtx,*i->status_map);
        while (tempG4.lex()){}
        in4.close();
    }


    out3.close();
    sqlFile.close();
    free(res);
    return 0;
}


